﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using StockholmsStad.Data.Interfaces;
using StockholmsStad.Utilities.Enums;
using StockholmsStad.Utilities.Exceptions;

namespace StockholmsStad.Data.DatabaseAgents
{
    internal abstract class DBAgentBase : IAgentBase
    {
        public AgentFactory AgentFactory
        {
            get { return new AgentFactory(); }
        }

        #region IAgentBase Members
        
        //Skriv om denna då den gör SELECT * FÖRST!!
        //public T SelectByPrimaryKey<T>(int id, StockholmsStadDataContext dbCtx) where T : class
        //{
        //    try
        //    {
        //        // hämta tabellen för typen T
        //        Table<T> table = dbCtx.GetTable<T>();

        //        // hämta metamodellen (databas -> domänobjekt)
        //        MetaModel modelMap = table.Context.Mapping;

        //        // hämta datamedlemmarna
        //        ReadOnlyCollection<MetaDataMember> dataMembers = modelMap.GetMetaType(typeof (T)).DataMembers;

        //        // hitta primärnyckeln
        //        // by checking for IsPrimaryKey
        //        string primaryKey = (dataMembers.Single(m => m.IsPrimaryKey)).Name;

        //        // hämta aktuellt objekt 
        //        return table.SingleOrDefault(
        //            delegate(T t)
        //                {
        //                    string memberId =
        //                        t.GetType().GetProperty(primaryKey).GetValue(t, null).
        //                            ToString();
        //                    return memberId == id.ToString();
        //                });
        //    }
        //    catch (Exception e)
        //    {
        //        throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
        //    }
        //}

        public List<T> SelectAll<T>(StockholmsStadDataContext dbCtx) where T : class
        {
            try
            {
                // hämta aktuell tabell och lista allt innehåll
                List<T> table = dbCtx.GetTable<T>().ToList();
                return table;
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }
        }

        public Type GetPrimaryKeyType<T>(StockholmsStadDataContext dbCtx) where T : class
        {
            try
            {
                Table<T> table = dbCtx.GetTable<T>();
                MetaModel modelMapping = table.Context.Mapping;
                ReadOnlyCollection<MetaDataMember> dataMembers = modelMapping.GetMetaType(typeof (T)).DataMembers;

                // hitta primärnyckeln och returnera dess typ
                return (dataMembers.Single(m => m.IsPrimaryKey)).Type;
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }
        }

        public string GetPrimaryKeyName<T>(StockholmsStadDataContext dbCtx) where T : class
        {
            try
            {
                Table<T> table = dbCtx.GetTable<T>();
                MetaModel modelMapping = table.Context.Mapping;
                ReadOnlyCollection<MetaDataMember> dataMembers = modelMapping.GetMetaType(typeof (T)).DataMembers;

                // hitta primärnyckeln och returnera dess namn
                return (dataMembers.Single(m => m.IsPrimaryKey)).Name;
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }
        }

        public int Insert<T>(T item, StockholmsStadDataContext dbCtx) where T : class
        {
            try
            {
                // hämta tabell och lägg till det nya objektet och submitta ändringarna
                Table<T> table = dbCtx.GetTable<T>();
                table.InsertOnSubmit(item);
                dbCtx.SubmitChanges();
                return (int) item.GetType().GetProperty(GetPrimaryKeyName<T>(dbCtx)).GetValue(item, null);
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }
        }

        public void Update<T>(T item, StockholmsStadDataContext dbCtx) where T : class
        {
            try
            {
                // skapa ny instans
                object newObj = Activator.CreateInstance(typeof (T), new object[0]);
                PropertyDescriptorCollection originalProps = TypeDescriptor.GetProperties(item);

                // se till att det nya objektet matchar befintligt
                foreach (PropertyDescriptor currentProp in originalProps)
                {
                    if (currentProp.Attributes[typeof (ColumnAttribute)] != null)
                    {
                        object val = currentProp.GetValue(item);
                        currentProp.SetValue(newObj, val);
                    }
                }

                // submitta ändringarna
                Table<T> table = dbCtx.GetTable<T>();
                table.Attach((T) newObj, true);
                dbCtx.SubmitChanges();
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }
        }

        public void Remove<T>(T item, StockholmsStadDataContext dbCtx) where T : class
        {
            try
            {
                Type tType = item.GetType();
                object newObj = Activator.CreateInstance(tType, new object[0]);

                // hämta egenskaperna för det bifogade objektet
                PropertyDescriptorCollection originalProps = TypeDescriptor.GetProperties(item);

                // kopiera objektet till det nya objektet med samma typ - detta ger oss ett objekt
                // som inte är kopplat till någon existerande datacontext
                foreach (PropertyDescriptor currentProp in originalProps)
                {
                    if (currentProp.Attributes[typeof (ColumnAttribute)] != null)
                    {
                        object val = currentProp.GetValue(item);
                        currentProp.SetValue(newObj, val);
                    }
                }

                // linq kräver att man attachar frånkopplat data (som i detta fall) till tabellen, sen tar bort det 
                // och submittar ändringarna
                Table<T> table = dbCtx.GetTable<T>();
                table.Attach((T) newObj, false);
                table.DeleteOnSubmit((T) newObj);
                dbCtx.SubmitChanges();
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }
        }

        #endregion
    }
}